﻿using System;
using System.Collections.Generic;
using System.Net;
using System.Linq;
using System.Web;
using Kaleida.ServiceMonitor.Model.Email;
using Kaleida.ServiceMonitor.Model.Runtime;

namespace Kaleida.ServiceMonitor.Model
{
    internal class NotificationMessageBuilder
    {
        private const string HtmlColorSuccess = "#0A0";
        private const string HtmlColourFailure = "#A00";
        private const string HtmlColourFailureFresh = "#D00";

        public NotificationMessage BuildSummary(Monitor monitor, OperationStatisticsMap statsSinceLastSummary)
        {
            var html = new HtmlMessageBuilder();

            AppendInstanceInformation(html);
            AppendMonitoringPeriod(html, monitor);

            html.AddRawFormat("<p>The severity level is currently {0}</p>", monitor.State.ToSeverityLevel().ToSummaryHtml());

            AppendErrorSummary(html, monitor.State);

            AppendCombinedAvailabilitySummary(html, statsSinceLastSummary, "Summary <span style='font-size:smaller'>(since last report)</span>".AsRawHtml());
            AppendAvailabilityTable(html, statsSinceLastSummary, "Availability <span style='font-size:smaller'>(since last report)</span>".AsRawHtml());
            AppendPerformanceTable(html, statsSinceLastSummary, "Performance <span style='font-size:smaller'>(since last report)</span>".AsRawHtml());

            AppendCombinedAvailabilitySummary(html, monitor.Statistics, "Summary <span style='font-size:smaller'>(since monitoring began)</span>".AsRawHtml());
            AppendAvailabilityTable(html, monitor.Statistics, "Availability <span style='font-size:smaller'>(since monitoring began)</span>".AsRawHtml());
            AppendPerformanceTable(html, monitor.Statistics, "Performance <span style='font-size:smaller'>(since monitoring began)</span>".AsRawHtml());

            AppendRecentResponses(html, monitor);

            var subject = string.Format("Scheduled Summary for Script '{0}'", monitor.Workspace.CurrentScript.Name);
            return new NotificationMessage(subject, html.ToString());
        }

		public NotificationMessage BuildThresholdChangeSummary(Monitor monitor, SeverityLevel mostRecentSeverityLevel)
        {
            var html = new HtmlMessageBuilder();

            AppendInstanceInformation(html);
            AppendMonitoringPeriod(html, monitor);

            html.AddRawFormat("<p>The severity threshold has changed from {0} to {1}</p>", mostRecentSeverityLevel.ToSummaryHtml(), monitor.State.ToSeverityLevel().ToSummaryHtml());

            AppendErrorSummary(html, monitor.State);

            AppendCombinedAvailabilitySummary(html, monitor.Statistics, "Summary".HtmlEncode());
            AppendAvailabilityTable(html, monitor.Statistics, "Availability".HtmlEncode());
            AppendPerformanceTable(html, monitor.Statistics, "Performance".HtmlEncode());
            AppendRecentResponses(html, monitor);

		    var subject = string.Format("ServiceMon Script {0} is {1}", monitor.Workspace.CurrentScript.Name, monitor.State.ToSeverityLevel());
            return new NotificationMessage(subject, html.ToString());
        }

        private static void AppendMonitoringPeriod(HtmlMessageBuilder html, Monitor monitor)
        {
            var scriptName = monitor.Workspace.CurrentScript.Name;
            var stats = monitor.Statistics.Combined;
            html.AddRawFormat("Script <b>{0}</b> has been running since {1:G}", scriptName.HtmlEncode(), stats.StartTime);
        }

        private static void AppendInstanceInformation(HtmlMessageBuilder html)
        {
            html.AddRawFormat("<p><i>Message sent at {0:G} from ServiceMon (v{1}), running on {2}</i></p>", DateTime.Now, ServiceMon.VersionNumber.ToString().HtmlEncode(), Dns.GetHostName().HtmlEncode());
        }

        private static void AppendRecentResponses(HtmlMessageBuilder html, Monitor monitor)
        {
            html.AddHeading(string.Format("Recent Responses (maximum of {0})", monitor.RecentResponses.MaximumItemCount).HtmlEncode());

            var bullets = monitor.RecentResponses.Items.Select(FormatResponse);

            html.AddBullets(bullets);
        }

        private static IHtmlString FormatResponse(RequestResult i)
        {
            var htmlColor = (i is RequestErrorResult) ? "red" : "black";
            return string.Format("<span style='color:{0}'>{1}</span>", htmlColor, i.ToListItemText().HtmlEncode()).AsRawHtml();
        }

        private static void AppendAvailabilityTable(HtmlMessageBuilder html, OperationStatisticsMap stats, IHtmlString heading)
        {
            html.AddHeading(heading);

            AppendStatisticsApplicableDateRange(html, stats);

            var availabilityTable = new BasicTable();
            availabilityTable.AddHeaderRow(new[] { "Operation", "Requests", "Successful", "Failed", "Success Rate" });
            foreach (var o in stats.Operations)
            {
                var successRate = o.GetSuccessRate();
                var successColor = o.SuccessCount > 0 ? HtmlColorSuccess : "#000";
                var failureColor = o.FailureCount > 0 ? HtmlColourFailure : "#000";

                availabilityTable.AddRow(new[]
                                             {
                                                 o.Description.HtmlEncode(),
                                                 o.ProcessedCount.ToString("0").HtmlEncode(),
                                                 string.Format("<span style='color:{0}'>{1:0}</span>", successColor, o.SuccessCount).AsRawHtml(),
                                                 string.Format("<span style='color:{0}'>{1:0}</span>", failureColor, o.FailureCount).AsRawHtml(),
                                                 (successRate == null ? "" : (successRate.Value * 100).ToString("N3") + "%").HtmlEncode()
                                             });
            }

            html.AddTable(availabilityTable);
        }

        private static void AppendStatisticsApplicableDateRange(HtmlMessageBuilder html, OperationStatisticsMap stats)
        {
            html.AddLine(string.Format("({0} to {1})", stats.Combined.StartTime, DateTime.Now).HtmlEncode());
        }

        private static void AppendCombinedAvailabilitySummary(HtmlMessageBuilder html, OperationStatisticsMap stats, IHtmlString heading)
        {
            html.AddHeading(heading);
            AppendStatisticsApplicableDateRange(html, stats);
            html.AddRawFormat("<b>{0}</b> Request{1} have been sent and received", stats.Combined.ProcessedCount, stats.Combined.ProcessedCount == 1 ? "" : "s");

            var successColor = stats.Combined.SuccessCount > 0 ? HtmlColorSuccess : "#000";
            var failureColor = stats.Combined.FailureCount > 0 ? HtmlColourFailure : "#000";

            html.AddBullets(new[]
                                {
                                    string.Format("<span style='color:{0}'><b>{1}</b> {2} successful</span>", successColor, stats.Combined.SuccessCount, stats.Combined.SuccessCount == 1 ? "was" : "were").AsRawHtml(),
                                    string.Format("<span style='color:{0}'><b>{1}</b> failed</span>", failureColor,stats.Combined.FailureCount).AsRawHtml()
                                });

            var successPercent = (100m*stats.Combined.SuccessCount)/stats.Combined.ProcessedCount;
            html.AddRawFormat("<p>The current success rate is <b>{0:N3}%</b></p>", successPercent);
        }

        private static void AppendPerformanceTable(HtmlMessageBuilder html, OperationStatisticsMap stats, IHtmlString heading)
        {
            html.AddHeading(heading);

            var performanceTable = new BasicTable();
            performanceTable.AddHeaderRow(new[] { "Operation", "Minimum", "Mean", "90th", "99th", "99.9th", "99.99th", "Maximum" });
            
            foreach(var o in stats.Operations)
            {
                var percentiles = o.GetResponseTimePercentiles(new[] {90m, 99m, 99.9m, 99.99m});

                var values = new List<IHtmlString>
                                 {
                                     o.Description.HtmlEncode(),
                                     GetMillisecondText(o.MinimumProcessingTime).HtmlEncode(),
                                     GetMillisecondText(o.MeanProcessingTime).HtmlEncode()
                                 };
                values.AddRange(percentiles.Select(i => GetMillisecondText(i.Value).HtmlEncode()));
                values.Add(GetMillisecondText(o.MaximumProcessingTime).HtmlEncode());
                performanceTable.AddRow(values);
            }
            
            html.AddTable(performanceTable);
        }

        private static void AppendErrorSummary(HtmlMessageBuilder html, MonitorState monitorState)
        {
            if (monitorState.HasOperationFailures)
            {
                html.AddBullets(BuildErrorList(monitorState));
            }
        }

        private static IEnumerable<IHtmlString> BuildErrorList(MonitorState monitorState)
        {
            var firstErrorHtml = monitorState.FirstOperationError.ToString().HtmlEncode();
            var latestErrorHtml = monitorState.LatestOperationError.ToString().HtmlEncode();

            if (firstErrorHtml != latestErrorHtml)
            {
                return new[]
                           {
                               string.Format("<span style='color:{0}'>First error:  {1}</span>", HtmlColourFailure, firstErrorHtml).AsRawHtml(),
                               string.Format("<span style='color:{0}'>Latest error: {1}</span>", HtmlColourFailureFresh, latestErrorHtml).AsRawHtml()
                           };
            }
            return new[]
                       {
                           string.Format("<span style='color:#D00'>Error details: {0}</span>", latestErrorHtml).AsRawHtml()
                       };
        }

        private static string GetMillisecondText(TimeSpan? value)
        {
            return value == null ? "n/a" : ((int)value.Value.TotalMilliseconds).ToString("0") + " ms";
        }
    }
}
